package com.zenithmind.disk.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zenithmind.disk.feign.UserFeignClient;
import com.zenithmind.disk.mapper.FilesMapper;
import com.zenithmind.disk.mapper.RecycleBinMapper;
import com.zenithmind.disk.pojo.domain.Files;
import com.zenithmind.disk.pojo.domain.RecycleBin;
import com.zenithmind.disk.pojo.vo.RecycleBinVO;
import com.zenithmind.disk.service.FilesService;
import com.zenithmind.disk.service.RecycleBinService;
import com.zenithmind.common.result.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

import com.zenithmind.disk.pojo.query.RecycleBinQuery;
import com.zenithmind.common.result.PageResult;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zenithmind.common.annotation.DataCache;
import com.zenithmind.common.annotation.DataCacheEvict;
import com.zenithmind.common.constant.CacheConstants;
import com.zenithmind.common.security.SecurityUtils;

@Slf4j
@Service
public class RecycleBinServiceImpl extends ServiceImpl<RecycleBinMapper, RecycleBin> implements RecycleBinService {
    @Autowired
    private RecycleBinMapper recycleBinMapper;
    @Autowired
    private FilesMapper fileMapper;
    @Qualifier("com.zenithmind.disk.feign.UserFeignClient")
    @Autowired
    private UserFeignClient userFeignClient;
    @Autowired
    private FilesService filesService;
    @Value("${file.upload.path}")
    private String uploadPath;
    
    // 缓存前缀常量定义
    private static final String CACHE_RECYCLE_PREFIX = "recycle";
    private static final String CACHE_USER_RECYCLE_PREFIX = "user:recycle";

    /**
     * 获取当前用户的回收站文件列表
     * 本方法通过JWT工具获取当前用户ID，构造查询条件，从数据库中查询该用户的所有回收站文件信息，
     * 并将其转换为RecycleBinVO对象列表返回
     *
     * @return 包含当前用户回收站文件信息的RecycleBinVO对象列表
     */
    @Override
    @DataCache(prefix = CACHE_USER_RECYCLE_PREFIX, key = "'list'", expireTime = CacheConstants.TTL_SECONDS_DEFAULT)
    public List<RecycleBinVO> listRecycleBin() {
        //获取当前用户ID
        String userId = SecurityUtils.getCurrentUserId();
        //创建Lambda查询条件，根据用户ID查询回收站中的文件信息
        LambdaQueryWrapper<RecycleBin> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(RecycleBin::getUserId, userId);
        //查询回收站中的文件信息列表
        List<RecycleBin> recycleBin = recycleBinMapper.selectList(lambdaQueryWrapper);
        //将查询到的回收站文件信息转换为RecycleBinVO对象列表
        List<RecycleBinVO> recycleBinVOS = recycleBin.stream().map(recycleBinData -> {
            RecycleBinVO recycleBinVO = new RecycleBinVO();
            //复制属性从RecycleBin对象到RecycleBinVO对象
            BeanUtils.copyProperties(recycleBinData, recycleBinVO);
            return recycleBinVO;
        }).toList();
        //返回转换后的RecycleBinVO对象列表
        return recycleBinVOS;
    }

    /**
     * 从回收站中恢复文件或文件夹
     *
     * @param fileId 文件或文件夹的ID
     * @param userId 用户ID
     * @return 如果恢复成功返回true，否则抛出异常
     * @throws RuntimeException 如果回收站中没有该文件，或者文件已被删除，或者用户无权限操作，抛出运行时异常
     */
    @Override
    @Transactional
    @DataCacheEvict(prefix = CACHE_USER_RECYCLE_PREFIX, key = "'list'")
    public boolean restoreFile(String fileId, String userId) {
        // 根据文件ID和用户ID查询回收站中的记录
        LambdaQueryWrapper<RecycleBin> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(RecycleBin::getFileId, fileId);
        queryWrapper.eq(RecycleBin::getUserId, userId);
        RecycleBin recycleBin = recycleBinMapper.selectOne(queryWrapper);
        
        if (recycleBin == null) {
            throw new RuntimeException("回收站中没有该文件");
        }
        
        // 查询文件表中的记录 (可能已被物理删除，但回收站记录还在)
        Files fileToRestore = fileMapper.selectById(fileId);
        if (fileToRestore == null) {
             // Even if file record is gone, remove the recycle bin record
             recycleBinMapper.deleteById(recycleBin.getId());
             log.warn("File record not found for fileId {} in recycle bin, removing recycle bin entry.", fileId);
             return true; // Indicate success as the bin entry is removed
             // Alternatively: throw new RuntimeException("原始文件记录不存在，无法恢复");
        }

        // 检查用户权限
        if (!fileToRestore.getUserId().equals(userId)) {
            throw new RuntimeException("用户无权限操作");
        }

        // 检查恢复目标路径是否已存在同名文件/文件夹
        String targetParentId = recycleBin.getOriginalPath(); // Recover to original parent
        LambdaQueryWrapper<Files> duplicateCheck = new LambdaQueryWrapper<>();
        duplicateCheck.eq(Files::getUserId, userId)
            .eq(Files::getFileName, fileToRestore.getFileName()) // Check original name
            .eq(Files::getIsDirectory, fileToRestore.getIsDirectory())
            .eq(Files::getStatus, 1); // Check against active files
        if (targetParentId == null) {
            duplicateCheck.isNull(Files::getParentId);
        } else {
            duplicateCheck.eq(Files::getParentId, targetParentId);
        }

        if (fileMapper.selectCount(duplicateCheck) > 0) {
            // Handle duplicate name - maybe rename the restored file?
            // For simplicity, let's throw an error for now.
            throw new RuntimeException("原始位置已存在同名文件或文件夹，无法恢复");
            // Or implement renaming logic similar to upload
        }

        // --- Begin Restoration Logic --- 

        // Calculate size to add back to user storage
        long sizeToIncrement = 0;
        if (fileToRestore.getIsDirectory() == 1) {
            // Need a way to calculate size of a *deleted* directory. 
            // This is tricky. Let's assume we restore the record but storage recalculation might be needed.
            // For now, we might skip precise storage update on folder restore or require a background job.
            log.warn("Calculating exact size for restoring deleted folder {} is complex. Storage update might be inaccurate.", fileId);
        } else {
            sizeToIncrement = fileToRestore.getFileSize(); // Size of the single file
        }

        // Restore the file/folder by setting status to 1 and updating parentId
        fileToRestore.setStatus(1);
        fileToRestore.setParentId(targetParentId); // Move back to original parent
        fileMapper.updateById(fileToRestore);
        log.info("Restored file/folder: id={}, name={}, userId={}", fileToRestore.getId(), fileToRestore.getFileName(), userId);
        
        // If it's a directory, recursively restore its contents (which should also have status 0)
        if (fileToRestore.getIsDirectory() == 1) {
            restoreDirectoryContentsRecursive(fileId, userId, targetParentId);
        }
        
        // Update user storage (Use Feign Client)
        if (sizeToIncrement > 0) {
             Result<Void> updateResult = userFeignClient.updateStorageUsed(userId, sizeToIncrement);
             if (updateResult == null || !updateResult.isSuccess()) { 
                 log.error("Failed to update user storage after restore: userId={}, sizeIncrement={}. Reason: {} - {}", 
                           userId, sizeToIncrement, 
                           updateResult != null ? updateResult.getCode() : "NULL_RESULT", 
                           updateResult != null ? updateResult.getMsg() : "Feign client returned null");
                 throw new RuntimeException("更新用户存储空间失败");
             }
        }

        // 删除回收站记录
        recycleBinMapper.deleteById(recycleBin.getId());
        
        return true;
    }
    
    /**
     * Recursively restores contents of a directory by setting their status to 1.
     * Assumes the parent directory (fileId) has already been restored.
     */
    private void restoreDirectoryContentsRecursive(String parentDirectoryId, String userId, String originalParentId) {
         LambdaQueryWrapper<Files> childrenWrapper = new LambdaQueryWrapper<>();
         // Find children that *were* under this parent when deleted
         // This requires knowing the structure at deletion time, which we might not have accurately.
         // A simpler, potentially less accurate approach: find *all* files with status 0 belonging to the user
         // and see if their *original* parent (if stored) matches parentDirectoryId.
         // Let's assume for now `deleteFileOrFolderRecursive` only set status = 0, keeping parentId.
         childrenWrapper.eq(Files::getParentId, parentDirectoryId)
                        .eq(Files::getUserId, userId)
                        .eq(Files::getStatus, 0); // Find deleted children of this folder
                       
         List<Files> children = fileMapper.selectList(childrenWrapper);
         for (Files child : children) {
            child.setStatus(1);
            fileMapper.updateById(child);
            log.info("Restored child file/folder: id={}, name={}, parentId={}", child.getId(), child.getFileName(), parentDirectoryId);
            if (child.getIsDirectory() == 1) {
                restoreDirectoryContentsRecursive(child.getId(), userId, child.getParentId()); // Recurse with the child's ID
            }
         }
    }

    /**
     * 永久删除文件或文件夹
     * 此方法用于从回收站中永久删除指定的文件或文件夹如果文件或文件夹不存在于回收站中，
     * 将抛出异常如果文件或文件夹存在，则会检查当前用户是否有权限进行删除操作
     * 对于文件夹，将递归删除其包含的所有文件和子文件夹对于文件，将删除其物理文件
     *
     * @param fileId 文件或文件夹的ID
     * @param userId 当前用户的ID
     * @return 如果删除成功，返回true；否则，可能抛出异常
     * @throws RuntimeException 如果文件不在回收站中或用户无权限操作
     */
    @Override
    @Transactional
    @DataCacheEvict(prefix = CACHE_USER_RECYCLE_PREFIX, key = "'list'")
    public boolean deleteForever(String fileId, String userId) {
        //查询回收站中的记录
        LambdaQueryWrapper<RecycleBin> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(RecycleBin::getFileId, fileId)
                .eq(RecycleBin::getUserId, userId);
        RecycleBin recycleBin = recycleBinMapper.selectOne(lambdaQueryWrapper);

        if (recycleBin == null) {
            // 文件不在回收站，可能已被彻底删除或从未放入
            log.warn("File {} not found in recycle bin for user {}. Assuming already deleted permanently.", fileId, userId);
            // Check if the actual file record still exists (status 0)
            Files fileRecord = fileMapper.selectById(fileId);
            if (fileRecord != null && fileRecord.getUserId().equals(userId)) {
                // Found the file record (status 0), proceed with physical deletion if needed
                deletePhysicalFileRecursive(fileRecord, userId); 
                fileMapper.deleteById(fileId); // Remove the file record from DB
            } else {
                 // No file record found, nothing more to do.
                 return true;
            }
        } else {
            // 文件在回收站中，继续处理
            Files fileToDelete = fileMapper.selectById(fileId);
            if (fileToDelete != null) {
                if (!fileToDelete.getUserId().equals(userId)) {
                    throw new RuntimeException("用户无权限操作");
                }
                 // 删除物理文件 (及其内容，如果是文件夹)
                 deletePhysicalFileRecursive(fileToDelete, userId); 
                 // 从数据库中删除文件记录
                 fileMapper.deleteById(fileId);
            } else {
                 log.warn("Recycle bin entry exists for fileId {}, but file record not found in Files table.", fileId);
            }
            // 删除回收站记录
            recycleBinMapper.deleteById(recycleBin.getId());
        }

        return true;
    }

    /**
     * Recursively deletes physical files/folders from disk.
     * Also removes the corresponding records from the Files table.
     */
    private void deletePhysicalFileRecursive(Files fileOrFolder, String userId) {
        if (fileOrFolder == null || !fileOrFolder.getUserId().equals(userId)) {
            return; // Safety check
        }

        if (fileOrFolder.getIsDirectory() == 1) {
            // Recursively delete contents first
            LambdaQueryWrapper<Files> childrenWrapper = new LambdaQueryWrapper<>();
            childrenWrapper.eq(Files::getParentId, fileOrFolder.getId())
                           .eq(Files::getUserId, userId);
            List<Files> children = fileMapper.selectList(childrenWrapper);
            for (Files child : children) {
                deletePhysicalFileRecursive(child, userId);
            }
            // Now delete the folder record itself from DB (physical folder deletion handled implicitly or by OS)
            // fileMapper.deleteById(fileOrFolder.getId()); // Deleting the parent record should cascade or be handled already
            log.info("Deleted folder record (and contents recursively): id={}, name={}", fileOrFolder.getId(), fileOrFolder.getFileName());

        } else {
            // It's a file, delete the physical file
            String absolutePath = getFileAbsolutePath(fileOrFolder);
            if (absolutePath != null) {
                File physicalFile = new File(absolutePath);
                if (physicalFile.exists()) {
                    if (physicalFile.delete()) {
                        log.info("Deleted physical file: {}", absolutePath);
                    } else {
                        log.warn("Failed to delete physical file: {}", absolutePath);
                        // Decide if this should throw an exception
                    }
                }
            } else {
                 log.warn("Could not get file path for physical deletion: fileId={}", fileOrFolder.getId());
            }
            // Delete the file record from DB
            // fileMapper.deleteById(fileOrFolder.getId()); // Deleting the parent record should cascade or be handled already
            log.info("Deleted file record: id={}, name={}", fileOrFolder.getId(), fileOrFolder.getFileName());
        }
        // Finally, delete the record passed to this function
        fileMapper.deleteById(fileOrFolder.getId());
    }

    /**
     * 清空当前用户的回收站
     *
     * @return 是否成功
     */
    @Override
    @Transactional
    @DataCacheEvict(prefix = CACHE_USER_RECYCLE_PREFIX, key = "'list'")
    public boolean clearRecycleBin() {
        //获取当前用户ID
        String userId = SecurityUtils.getCurrentUserId();
        if (userId == null) {
            throw new RuntimeException("无法获取用户信息");
        }

        // 查询用户回收站中的所有记录
        LambdaQueryWrapper<RecycleBin> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(RecycleBin::getUserId, userId);
        List<RecycleBin> recycleBinItems = recycleBinMapper.selectList(queryWrapper);

        if (recycleBinItems.isEmpty()) {
            return true; // Nothing to clear
        }

        // 遍历回收站记录，执行永久删除逻辑
        for (RecycleBin item : recycleBinItems) {
             try {
                 deleteForever(item.getFileId(), userId); // Reuse the permanent delete logic
             } catch (Exception e) {
                  log.error("Error during clearRecycleBin for fileId {}: {}", item.getFileId(), e.getMessage(), e);
                  // Decide how to handle partial failure: continue? rollback? throw?
                  // For now, log and continue
             }
        }

        // Ensure all bin entries are deleted even if file deletion had issues
        recycleBinMapper.delete(queryWrapper);

        return true;
    }

    /**
     * 分页查询回收站文件列表
     *
     * @param recycleBinQuery 回收站查询对象，包含查询参数和分页信息
     * @return 返回分页查询结果，包含回收站文件列表和分页相关数据
     */
    @Override
    @DataCache(prefix = CACHE_USER_RECYCLE_PREFIX, key = "#recycleBinQuery.userId + ':page' + #recycleBinQuery.current", 
              expireTime = CacheConstants.TTL_SECONDS_DEFAULT)
    public PageResult<RecycleBinVO> recycleBinPage(RecycleBinQuery recycleBinQuery) {
        Page<RecycleBin> page = recycleBinQuery.toPage();

        Page<RecycleBin> recycleBinPage = lambdaQuery()
            .eq(StringUtils.isNotBlank(recycleBinQuery.getUserId()), RecycleBin::getUserId, recycleBinQuery.getUserId())
            .orderByDesc(RecycleBin::getDeleteTime)
            .page(page);

        // 转换为VO对象
        List<RecycleBinVO> recycleBinVOList = recycleBinPage.getRecords().stream().map(recycleBin -> {
            RecycleBinVO recycleBinVO = new RecycleBinVO();
            BeanUtils.copyProperties(recycleBin, recycleBinVO);
            return recycleBinVO;
        }).collect(Collectors.toList());

        return new PageResult<>(recycleBinVOList, recycleBinPage.getTotal(), recycleBinPage.getCurrent(), recycleBinPage.getSize());
    }

    /**
     * 批量删除回收站文件-管理员
     *
     * @param ids 回收站记录ID列表
     * @return 是否成功
     */
    @Override
    @Transactional
    @DataCacheEvict(prefix = CACHE_RECYCLE_PREFIX, key = "'*'")
    public boolean deleteBatchRecycleBin(List<String> ids) {
        if (ids == null || ids.isEmpty()) {
            return true;
        }

        // 查询要删除的回收站记录
        List<RecycleBin> recycleBins = listByIds(ids);
        if (recycleBins.isEmpty()) {
            return true;
        }

        // 遍历所有回收站记录，执行永久删除
        for (RecycleBin recycleBin : recycleBins) {
            try {
                // 检查关联的文件是否存在
                Files fileToDelete = fileMapper.selectById(recycleBin.getFileId());
                if (fileToDelete != null) {
                    // 删除物理文件
                    deletePhysicalFileRecursive(fileToDelete, recycleBin.getUserId());
                    // 从数据库中删除文件记录
                    fileMapper.deleteById(recycleBin.getFileId());
                }
                // 删除回收站记录
                recycleBinMapper.deleteById(recycleBin.getId());
            } catch (Exception e) {
                log.error("Error during batch delete for recycleBin id {}: {}", recycleBin.getId(), e.getMessage(), e);
                // 继续处理其他记录
            }
        }

        return true;
    }

    /**
     * 管理员添加或修改回收站记录
     *
     * @param recycleBin 回收站记录
     * @return 回收站记录
     */
    @Override
    @Transactional
    @DataCacheEvict(prefix = CACHE_RECYCLE_PREFIX, key = "#recycleBin.id")
    public RecycleBinVO addOrUpdateRecycleBinByAdmin(RecycleBin recycleBin) {
        // 如果是修改操作
        if (StringUtils.isNotBlank(recycleBin.getId())) {
            RecycleBin existingRecycleBin = getById(recycleBin.getId());
            if (existingRecycleBin == null) {
                throw new RuntimeException("回收站记录不存在");
            }
            
            // 保存修改后的回收站记录
            updateById(recycleBin);
        } else {
            // 添加操作，检查文件是否存在
            Files file = fileMapper.selectById(recycleBin.getFileId());
            if (file == null) {
                throw new RuntimeException("关联的文件不存在");
            }
            
            // 设置默认值
            if (recycleBin.getDeleteTime() == null) {
                recycleBin.setDeleteTime(LocalDateTime.now());
            }
            
            save(recycleBin);
        }
        
        // 返回更新后的回收站记录
        RecycleBinVO recycleBinVO = new RecycleBinVO();
        BeanUtils.copyProperties(getById(recycleBin.getId()), recycleBinVO);
        return recycleBinVO;
    }

    /**
     * 获取文件的绝对路径
     * @param file 文件实体
     * @return 文件绝对路径
     */
    private String getFileAbsolutePath(Files file) {
        if (file == null || StringUtils.isBlank(file.getFilePath())) {
            return null;
        }

        // 如果文件路径已经是绝对路径，直接返回
        if (file.getFilePath().startsWith("/") || file.getFilePath().contains(":")) {
            return file.getFilePath();
        }

        // 否则拼接上传路径
        return uploadPath + File.separator + file.getFilePath();
    }
}
